const express = require('express');
const crypto = require('@wecom/crypto');
const xml2js = require('xml2js');
const { promisify } = require('util');
const ffi = require('ffi-napi');
const ref = require('ref-napi');
const Struct = require('ref-struct-di')(ref);
const path = require('path');
const fs = require('fs');

// 配置信息 - 从企业微信后台获取
const TOKEN = process.env.WECOM_TOKEN || 'ipjSrT9pDDq0eHWN2HsAw5';
const ENCODING_AES_KEY = process.env.WECOM_ENCODING_AES_KEY || 'J7rGrFV99ASDo9fVaiNYsMQkxWOafGTFdWZkIN4YY4L'; 
const CORP_ID = process.env.WECOM_CORP_ID || 'wx9e04d92a62846a6a';
const SECRET = process.env.WECOM_SECRET || 'OQ7zbA7k_VgHXlk_pzgXz_dL-94KhTIlFOwna3brKDc'; // 从企业微信后台获取
const PORT = process.env.PORT || 3000;

// RSA 私钥
const PRI_KEY = `-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXyWSEDksPGry6
CKy+oFMSPWD3NZA4BqdsOBjd4LUJrH1CD/SZpsj5vuP2rCtROP1S+EJUaJegWnaq
gZxOkE1ny8HX+82FzSrGRWXREAaIEBl57SyX6T/ep+wCuaxvm4jy/drvKrGEIHRs
KKn4FqCKrgnW4c39Q0xfIAEYf/SJ1UivOFAXGrEmllIgEz+p94sl4yLhfI5I84L6
8qghlI7OVSZ6nP1TSN6WPuvIWzHgGWxK0LFJ4H/zJPDNN1AUbeTn1c8AUKjI6seK
MXofK9qLU/8QaILARdLreAHTWcP+Fl7j5tRH82+R+1c3zOdaWpUvae1IVOoTOtpO
3DD+AhibAgMBAAECggEAEidPOG9ghm8bXkvro9BB+d78ag9DxJJLKNQcE+wBoFAQ
b7u/yeFa4mp0Z6FXfEXgH7G7ISRHP4bs8AnuBgQX4I+hNfHoNpX/Wn+cpKCRihQU
L/MapRU2WIIQkJI2BOfUZ8IjvVIHsXaTkLIGWLpqu1tYUupmZrbLULSKXQHvXyKY
1hK2PorA8OBGk8dEcIq0TmQ174NnZr3z5QTzdPIG72FkOHtsXo7spmb6HLhEBGWE
PAQZU9etX3V2t2YcHdJ9+40XDIuLHSMaYImkBEzc5VdJehSwRoybHOjBD7GM2vI5
2r/Drrs4YN5SogM1byMx6mmM36SzS5Z+BGkg4OzUWQKBgQDHMJ7eL/9aa2TygNv+
Up3Yh8xIuG2xMAifLvF9iFPR8nsSRih/nIJ1rR0V0GvMZjciQKmV3off5LcHVrKM
PrNHlwzoUbI2phQ8CQwPImmDl4teQ8IV1eGrgBVJDcCiJuOilyhVtJRpC476gAqP
/MgRqVLUfP7SKzowYB1GBOBiaQKBgQDDE7zKLokqmNDbdAWOC6/qgfvza+t08WtQ
C9bo4LhwCdU5uxEdZ1LgbWHP7N9iiWsfVExJGON/NiqaekDH/vLbb08r4VlPEECj
JtPu4lGcPDxJHX2l10LpNhOq4cOumlfcZo77FXZBKhtQim5nrz8/v9R7QShIh1QT
ZxGHcuZ6YwKBgCVx3mumWjqFNdTgpHqKzWGHJcAF65kDjpteF8eheSBelQQ6bCKR
KXU1teID7D/gdzzZKwghbSmJtV6LzcTPhKRQoPyLXxUm42blzH6L2YoDUw/FbRbt
vh6fVWEGVNAmv2QxRkPWTo/wvFAbAeFPg8zOJ+QKG10nAylU6Cvhfs0BAoGAZn14
MsFAT6D6GlJamMaLJioktT999XLhbqzJvJBUVojT/gDKfShL4p2VRiitNJf+L/ea
HM12uN88guy4HFtWPPO+JM/AnEltrEo6DsfZGLyM790OppLyFJTUwHLc3F86yhEY
DdK4zoPQ1f/lAZSRJm0itZ8GCxUOlyMPkppLfQECgYB6RXrkXQFh1VVE0Z+dbTA0
PalHgUis5aX5Wh+Y6/p1BIuoOGugz4TZr7iUgfRpmZ01YVp+yMRWOwfZyK+AR8/a
1Zhx0su0qGeFSbSpUYJdIHUhAjQTe1fAbGJsEKmr68ITIe4SQafi7M61+cVwRMVq
SMp33dYbnHguezlApy0MbA==
-----END PRIVATE KEY-----`;

// XML 解析器配置
const parseXML = promisify(new xml2js.Parser({ explicitArray: false, trim: true }).parseString);
const builder = new xml2js.Builder({ cdata: true, headless: true });

// FFI 定义 SDK 结构体
const Slice_t = Struct({
  'buf': 'string',
  'len': 'uint32'
});
const MediaData_t = Struct({
  'data': 'string',
  'data_len': 'uint32',
  'outindex_buf': 'string',
  'is_finish': 'int'
});
const WeWorkFinanceSdk_t = ref.types.void;
const WeWorkFinanceSdk_tPtr = ref.refType(WeWorkFinanceSdk_t);
const Slice_tPtr = ref.refType(Slice_t);
const MediaData_tPtr = ref.refType(MediaData_t);

// FFI 加载 .so 文件
const sdkLib = ffi.Library(path.join(__dirname, 'libs/libWeWorkFinanceSdk_C.so'), {
  'NewSdk': [WeWorkFinanceSdk_tPtr, []],
  'Init': ['int', [WeWorkFinanceSdk_tPtr, 'string', 'string']],
  'GetChatData': ['int', [WeWorkFinanceSdk_tPtr, 'uint64', 'uint64', 'string', 'string', 'uint64', Slice_tPtr]],
  'GetMediaData': ['int', [WeWorkFinanceSdk_tPtr, 'string', 'string', 'string', 'string', 'uint64', MediaData_tPtr]],
  'DecryptData': ['int', ['string', 'string', Slice_tPtr]],
  'GetContentFromSlice': ['string', [Slice_tPtr]],
  'GetData': ['string', [MediaData_tPtr]],
  'GetDataLen': ['uint32', [MediaData_tPtr]],
  'GetOutIndexBuf': ['string', [MediaData_tPtr]],
  'IsMediaDataFinish': ['int', [MediaData_tPtr]],
  'FreeSlice': ['void', [Slice_tPtr]],
  'FreeMediaData': ['void', [MediaData_tPtr]],
  'DestroySdk': ['void', [WeWorkFinanceSdk_tPtr]]
});

// 初始化 SDK
const sdk = sdkLib.NewSdk();
const initResult = sdkLib.Init(sdk, CORP_ID, SECRET);
console.log('initResult',initResult)
if (initResult !== 0) {
  console.error('SDK 初始化失败:', initResult);
  process.exit(1);
}

// RSA 解密函数
function rsaDecryptChatData(encryptData) {
  const crypto = require('crypto');
  try {
    const privateKey = PRI_KEY;
    const buffer = Buffer.from(encryptData, 'base64');
    const decrypted = crypto.privateDecrypt(
      {
        key: privateKey,
        padding: crypto.constants.RSA_PKCS1_PADDING
      },
      buffer
    );
    return decrypted.toString('utf8');
  } catch (error) {
    console.error('RSA 解密失败:', error);
    return null;
  }
}

const app = express();

// 中间件配置
app.use(express.urlencoded({ extended: true }));
app.use(express.text({ type: 'text/xml' }));

// GET 请求验证 URL 有效性
app.get('/callback', (req, res) => {
  // 1. 解析参数
  const { msg_signature, timestamp, nonce, echostr } = req.query;
  console.log('打印了',msg_signature, timestamp, nonce, echostr)
  // 2. 验证参数是否存在
  if (!msg_signature || !timestamp || !nonce || !echostr) {
    return res.status(400).send('参数缺失');
  }

  // 3. 验证签名
  const calculatedSignature = crypto.getSignature(
    TOKEN, 
    timestamp, 
    nonce, 
    echostr
  );

  if (calculatedSignature !== msg_signature) {
    console.error('签名验证失败', {
      接收签名: msg_signature,
      计算签名: calculatedSignature
    });
    return res.status(403).send('签名验证失败');
  }

  // 4. 解密消息
  const { message } = crypto.decrypt(ENCODING_AES_KEY, echostr);
  
  // 5. 返回明文消息内容
  res.set('Content-Type', 'text/plain');
  console.log('消息',message)
  res.send(message);
});

// ======================
// POST请求处理消息接收
// ======================
app.post('/callback', async (req, res) => {
  try {
    // 1. 解析URL参数
    const { msg_signature, timestamp, nonce } = req.query;
    
    if (!msg_signature || !timestamp || !nonce) {
      return res.status(400).send('参数缺失');
    }

    // 2. 解析XML请求体
    const xmlData = req.body;
    const result = await parseXML(xmlData);
    const encryptMsg = result.xml.Encrypt;
    
    if (!encryptMsg) {
      return res.status(400).send('无效的消息格式');
    }

    // 3. 验证签名
    const calculatedSignature = crypto.getSignature(
      TOKEN, 
      timestamp, 
      nonce, 
      encryptMsg
    );

    if (calculatedSignature !== msg_signature) {
      console.error('签名验证失败', {
        接收签名: msg_signature,
        计算签名: calculatedSignature
      });
      return res.status(403).send('签名验证失败');
    }

    // 4. 解密消息
    const decrypted = crypto.decrypt(ENCODING_AES_KEY, encryptMsg);
    const message = await parseXML(decrypted.message);
    
    console.log('收到消息:', JSON.stringify(message.xml, null, 2));
    
    // 5. 处理消息并回复
    const response = await handleMessage(message.xml);
    
    if (response) {
      // 6. 加密回复消息
      const encryptResponse = crypto.encrypt(ENCODING_AES_KEY, response);
      const newTimestamp = Math.floor(Date.now() / 1000).toString();
      const newNonce = Math.random().toString(36).substring(2, 15);
      
      // 7. 生成新签名
      const responseSignature = crypto.getSignature(
        TOKEN,
        newTimestamp,
        newNonce,
        encryptResponse
      );
      
      // 8. 构造响应XML
      const responseXml = builder.buildObject({
        xml: {
          Encrypt: encryptResponse,
          MsgSignature: responseSignature,
          TimeStamp: newTimestamp,
          Nonce: newNonce
        }
      });
      
      res.set('Content-Type', 'application/xml');
      console.log('post',responseXml)
      return res.send(responseXml);
    }
    
    // 不需要回复时返回空响应
    res.status(200).end('');
    
  } catch (error) {
    console.error('处理消息时出错:', error);
    res.status(500).send('服务器内部错误');
  }
});

// 新增路由：获取会话存档
app.get('/archive', async (req, res) => {
  try {
    const seq = parseInt(req.query.seq || 0);
    const limit = parseInt(req.query.limit || 10);
    const timeout = 10000; // 超时时间，单位毫秒
    const proxy = ''; // 代理地址（可选）
    const passwd = ''; // 代理密码（可选）

    const chatDatas = new Slice_t();
    const ret = sdkLib.GetChatData(sdk, seq, limit, proxy, passwd, timeout, chatDatas.ref());
    console.log('参数',sdk, seq, limit, proxy, passwd, timeout)
    console.log('聊天记录',ret)
    if (ret !== 0) {
      console.error('获取聊天记录失败:', ret);
      return res.status(500).json({ error: '获取聊天记录失败', code: ret });
    }

    const content = sdkLib.GetContentFromSlice(chatDatas.ref());
    const messages = JSON.parse(content);
    const decryptedMessages = [];

    for (const msg of messages.chatdata) {
      const encryptKey = rsaDecryptChatData(msg.encrypt_random_key);
      if (!encryptKey) {
        console.error('解密密钥失败:', msg.encrypt_random_key);
        continue;
      }
      const decryptedSlice = new Slice_t();
      const decryptRet = sdkLib.DecryptData(encryptKey, msg.encrypt_chat_msg, decryptedSlice.ref());
      if (decryptRet !== 0) {
        console.error('解密消息失败:', decryptRet);
        continue;
      }
      const decryptedContent = sdkLib.GetContentFromSlice(decryptedSlice.ref());
      decryptedMessages.push(JSON.parse(decryptedContent));
      sdkLib.FreeSlice(decryptedSlice.ref());
    }

    sdkLib.FreeSlice(chatDatas.ref());
    res.json({ messages: decryptedMessages });
  } catch (error) {
    console.error('获取会话存档出错:', error);
    res.status(500).json({ error: '服务器内部错误' });
  }
});

// 消息处理函数
async function handleMessage(message) {
  const msgType = message.MsgType;
  if (msgType === 'text') {
    return buildTextMessage(
      message.FromUserName,
      message.ToUserName,
      `已收到您的消息：${message.Content}`
    );
  }
  if (msgType === 'event') {
    const eventType = message.Event;
    switch (eventType) {
      case 'subscribe':
        return buildTextMessage(
          message.FromUserName,
          message.ToUserName,
          '欢迎关注！发送任意消息开始互动'
        );
      case 'unsubscribe':
        console.log(`用户 ${message.FromUserName} 取消关注`);
        return null;
      case 'click':
        return buildTextMessage(
          message.FromUserName,
          message.ToUserName,
          `您点击了菜单: ${message.EventKey}`
        );
    }
  }
  return buildTextMessage(
    message.FromUserName,
    message.ToUserName,
    `暂不支持${msgType}类型的消息`
  );
}

// 构建文本回复消息
function buildTextMessage(toUser, fromUser, content) {
  return builder.buildObject({
    xml: {
      ToUserName: { _: toUser },
      FromUserName: { _: fromUser },
      CreateTime: Math.floor(Date.now() / 1000),
      MsgType: { _: 'text' },
      Content: { _: content }
    }
  });
}

// 启动服务
app.listen(PORT, () => {
  console.log(`=============================================`);
  console.log(`企业微信回调服务已启动`);
  console.log(`服务地址: http://localhost:${PORT}/callback`);
  console.log(`存档接口: http://localhost:${PORT}/archive?seq=0&limit=100`);
  console.log(`Token: ${TOKEN}`);
  console.log(`EncodingAESKey: ${ENCODING_AES_KEY}`);
  console.log(`CorpID: ${CORP_ID}`);
  console.log(`Secret: ${SECRET}`);
  console.log('配置说明:');
  console.log('1. 在企业微信后台配置回调URL时使用 /callback');
  console.log('2. 确保填入相同的Token和EncodingAESKey');
  console.log('3. 支持接收文本消息和事件消息');
  console.log('4. 使用 /archive 获取会话存档');
  console.log('=============================================');
});

// 清理 SDK
process.on('exit', () => {
  sdkLib.DestroySdk(sdk);
});